/*  Copyright 2012 Bosch Mickaël - Brousset Mathias - Tellier Léo - Thomas Clément
 *
 *  This file is part of shared-drawing.
 * 
 *  shared-drawing is free software: you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free Software
 *  Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 *  shared-drawing is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License along with
 *  shared-drawing. If not, see http://www.gnu.org/licenses/.
 */

/**
 * @file Server.java
 * This class is a server
 *
 */

package server;

import SharedDrawing.Action;
import common.XmlConstants;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import org.apache.commons.cli.*;


/** @class Server
 *  This is the server of the shared-drawing
 * 
 */
public class Server {
    
    private static Server ref;
    
    static private ArrayList listWorkgroups = new ArrayList();
    static private ArrayList listAllClients = new ArrayList();
    static private Integer nb_allclients = 0;
    static private Integer nb_workgroups = 0;
    
    static private Integer CTLIMIT = 50;
    static private Integer WGLIMIT = 10;
    
    /** 
     * Constructor
     * The constructor is private because this class is a singleton
     */
    private Server() {
        super();
    }
    
    /**
     * clone() Not Supported
     * @return
     * @throws CloneNotSupportedException
     * This class is a singleton
     */
    @Override
    public Object clone() throws CloneNotSupportedException
    {
        throw new CloneNotSupportedException(); 
    }
    
    /** 
     * Get the single instance of Server
     * @return A reference to the Server
     * Or create it if not invoked yet
     */
    public static synchronized Server getServer() {
        if (ref == null) {
            ref = new Server();
        }
        return ref;
    }
    
    /** 
     * Add a Workgroup to the Server
     * @param t The WorkGroupThread target
     * @return 
     */
    synchronized public int addWorkgroup(WorkGroupThread t) {
        nb_workgroups++;
        listWorkgroups.add(t);
        return listWorkgroups.size()-1;
    }
    
    /**
     * Delete a Workgroup of the Server
     * @param t The WorkGroupThread target
     */
    synchronized public void delWorkgroup(WorkGroupThread t) {
        delWorkgroup(listWorkgroups.indexOf(t));
    }

    /**
     * Delete a Workgroup of the Server
     * @param i The id of the WorkGroupThread target
     */
    synchronized public void delWorkgroup(int i) {
        if (listWorkgroups.get(i) != null)
        {
            listWorkgroups.remove(i);
            nb_workgroups--;
        }
    }
    
    /**
     * Return the number of Workgroup on the server
     * @return number of Workgroup
     */
    synchronized public double getNbWorkgroups() {
        return nb_workgroups;
    }
    
    /**
     * Add a Client to the Server
     * @param t The ClientThread target
     * @return  
     */
    synchronized public int addClient(ClientThread t) {
        nb_allclients++;
        listAllClients.add(t);
        return listAllClients.size()-1;
    }
    
    /**
     * Delete a Client of the Server
     * @param i The id of the Client target
     */
    synchronized public void delClient(ClientThread client) {
        int i = listAllClients.indexOf(client);
        if(i != -1)
        {
            listAllClients.remove(i);
            if (CTLIMIT * WGLIMIT == nb_allclients) {
                synchronized(this) {
                    notify();
                }
            }
            nb_allclients--;
        }
        else
        {
            System.out.println("didn't find the client to remove");
        }
    }
    
    /**
     * Return the number of Client on the server
     * @return number of Client
     */
    synchronized public double getNbClients() {
        return nb_allclients;
    }

    /** 
     * Add a ClientThread to a Workgroup
     * @param client The ClientThread target
     * @param nick The Client's nickname
     * @param wgName The name of the Workgroup
     * @param wgPassword The password of the Workgroup
     * @return The id of the client in the Workgroup
     * A WorkGroupThread is created if needed using the password 
     */
    synchronized public Integer toWorkGroup(ClientThread client, String nick,
            String wgName, String wgPassword) {
        WorkGroupThread tmp;
        for (int i = 0; i < listWorkgroups.size(); i++)
        {
            tmp = (WorkGroupThread) listWorkgroups.get(i);
            if (tmp != null) {
                if (tmp.getName().equals(wgName)) {
                    if (tmp.getHash().equals(wgPassword))
                    {
                        client.setWorkGroup(tmp);
                        return tmp.addClient(client, nick);
                    }
                    else
                    {
                        Action action = new Action(XmlConstants.error);
                        action.addContent(XmlConstants.message,
                            "wrong password");
                        client.send(action);
                        return -1;
                    }
                }
            }
        }
        if ( nb_workgroups < WGLIMIT) {
            tmp = new WorkGroupThread(wgName,wgPassword);
            listWorkgroups.add(tmp);
            client.setWorkGroup(tmp);
            //the first client of a work group is up to date
            client.setUpToDate(true);
            tmp.start();
            return tmp.addClient(client, nick);
        } else {
            Action action = new Action(XmlConstants.error);
            action.addContent(XmlConstants.message,
                    "Workgroup limit reached.");
            client.send(action);
            return -1;
        }
    }

    /**
     * Server Main
     * @param args
     * @throws IOException
     */
    public static void main(String []args) throws IOException, InterruptedException
    {
        Integer port = 18000;//Default port
        
        try {
            Options opt = new Options();

            opt.addOption("h", "help", false, "Display the help of the server.");
            opt.addOption("p", "port", true, "Change the port of the server.");
            opt.addOption("nbwg", "nbworkgroup", true, "Define the limit number of workgroup.");
            opt.addOption("nbclt", "nbclient", true, "Define the max number of client per workgroup.");

            BasicParser parser = new BasicParser();
            CommandLine cl = parser.parse(opt, args);

            if ( cl.hasOption('h') ) { //| cl.hasOption("help") ) {
                HelpFormatter f = new HelpFormatter();
                f.printHelp("OptionsTip", opt);
                System.exit(0);
            }
            else { 
                if (cl.hasOption('p') ) {
                    port = Integer.parseInt(cl.getOptionValue("p"));
                }
                               
                if (cl.hasOption("nbwg") ) {
                    WGLIMIT = Integer.parseInt(cl.getOptionValue("nbwg"));
                }
                 
                if (cl.hasOption("nbclt") ) {
                    CTLIMIT = Integer.parseInt(cl.getOptionValue("nbclt"));
                }
            }
        }
        catch (ParseException e) {
            e.printStackTrace();
        }
        
        Socket newClient;
        ClientThread newCThread;
        
        ServerSocket ss = new ServerSocket(port.intValue());
        
        while (true)
        {
            newClient = ss.accept();
            if (CTLIMIT * WGLIMIT > nb_allclients) {    
                newCThread = new ClientThread(newClient);
                Server.getServer().addClient(newCThread);
                newCThread.start();
                Action action = new Action(XmlConstants.connect);
                newCThread.send(action);
            } else {
                Action action = new Action(XmlConstants.connectRefused);
                action.addContent(XmlConstants.message, "too much clients");
                newCThread = new ClientThread(newClient);
                newCThread.send(action);
                newClient.close();
                System.out.println("[Server] No more client please.");
                synchronized(Server.getServer()) {
                    Server.getServer().wait();
                }
            }

        }
    }
    
}
